Skip to content

fix(objectql): thread execution context into read-time formula evaluation#1988

Merged
os-zhuang merged 1 commit into
mainfrom
fix/applyformulaplan-context-1979
Jun 16, 2026
Merged

fix(objectql): thread execution context into read-time formula evaluation#1988
os-zhuang merged 1 commit into
mainfrom
fix/applyformulaplan-context-1979

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #1979.

What

applyFormulaPlan — which computes Field.formula virtual fields after find/findOne — evaluated each expression with only { record }:

const r = ExpressionEngine.evaluate(fp.expression, { record: rec });

So:

  • a formula using now()/today() ran against a fresh wall-clock read on every evaluation — no determinism within one read;
  • a formula referencing the caller (os.user.id, os.org.id) faulted and fell back to null, because user/org were never in scope.

Fix

Build the eval context exactly the way applyFieldDefaults already does — a now pinned once per operation (every row and every formula field in one read observes the same instant) plus os.user / os.org resolved from the ExecutionContext:

const now = nowSnapshot ?? new Date();
const user = execCtx?.userId ? { id: String(execCtx.userId), role: execCtx?.roles?.[0] } : undefined;
const org  = execCtx?.tenantId ? { id: String(execCtx.tenantId) } : undefined;
// …evaluate(fp.expression, { now, user, org, record: rec })

The two call sites in find()/findOne() pass opCtx.context (already in scope). Read-time formulas now behave consistently with default-value expressions.

Scope

This is the read-path slice of ADR-0053 Phase 2 (#1975), but it is independent of timezone and valuable on its own (determinism + caller-aware computed fields). #1980 will additionally thread timezone here once ExecutionContext.timezone exists.

Testing

  • New: a read-time formula resolves os.user.id / os.org.id from the execution context; now() is pinned identically across all rows in one find.
  • Full @objectstack/objectql suite green — 639 tests; DTS build + typecheck clean.
  • Changeset: patch (lockstep fixed group).

🤖 Generated with Claude Code

…tion (#1979)

`applyFormulaPlan` computes Field.formula virtual fields after find/findOne,
but evaluated each expression with only `{ record }`. So a formula using
now()/today() ran against a fresh wall-clock read on every evaluation (no
determinism), and a formula referencing the caller (os.user.id / os.org.id)
faulted and fell back to null because user/org were never in scope.

Build the eval context the same way applyFieldDefaults already does: a `now`
snapshot pinned once per operation (every row + every formula field in one
read observes the same instant) plus os.user / os.org resolved from the
ExecutionContext. The two call sites in find()/findOne() pass opCtx.context.

Independent of timezone; the read-path prerequisite for ADR-0053 Phase 2
(#1980 will additionally thread `timezone` here once ExecutionContext.timezone
exists).

Tests: read-time formula resolves os.user.id/os.org.id from context; `now()`
is pinned identically across all rows in one find. Full objectql suite green
(639). Closes #1979.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 16, 2026 10:25pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jun 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql.

14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/core/services.mdx (via @objectstack/objectql)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/objectql)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/concepts/packages.mdx (via @objectstack/objectql)
  • content/docs/guides/authentication.mdx (via @objectstack/objectql)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/objectql)
  • content/docs/guides/formula.mdx (via packages/objectql)
  • content/docs/guides/kernel-services.mdx (via @objectstack/objectql)
  • content/docs/guides/objectql-migration.mdx (via @objectstack/objectql)
  • content/docs/guides/packages.mdx (via @objectstack/objectql)
  • content/docs/guides/plugins.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 67c29ee into main Jun 16, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the fix/applyformulaplan-context-1979 branch June 16, 2026 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADR-0053 Phase 2 · Slice 2: thread context into applyFormulaPlan (read-time formula fields)

1 participant